package steak.xteamy.VoiceChat;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import de.maxhenkel.voicechat.api.Group;
import de.maxhenkel.voicechat.api.VoicechatConnection;
import de.maxhenkel.voicechat.api.VoicechatServerApi;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.minecraft.block.Blocks;
import net.minecraft.command.CommandSource;
import net.minecraft.command.DataCommandStorage;
import net.minecraft.command.argument.EntityArgumentType;
import net.minecraft.command.argument.IdentifierArgumentType;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.ProfileComponent;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.effect.StatusEffect;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.particle.BlockStateParticleEffect;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.predicate.NbtPredicate;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper;
import steak.xteamy.KillerGroup;
import steak.xteamy.Network.Packets;
import steak.xteamy.VoiceChat.Pop.Bug;

import java.util.*;
import java.util.concurrent.CompletableFuture;

public class Command {
    private static final Gson GSON = new Gson();

    public static CommandRegistrationCallback Init() {
        return (commandDispatcher, commandRegistryAccess, registrationEnvironment) ->
                commandDispatcher.register(CommandManager.literal(KillerGroup.MOD_ID).requires(source -> source.hasPermissionLevel(2))

                        .then(CommandManager.literal("create")
                                .then(CommandManager.argument("GroupName", StringArgumentType.string())
                                        .then(CommandManager.argument("GroupType", StringArgumentType.string()).suggests((commandContext, suggestionsBuilder) -> suggestGroupType(suggestionsBuilder))
                                                .executes(Command::create))))

                        .then(CommandManager.literal("remove")
                                .then(CommandManager.argument("GroupName", StringArgumentType.string()).suggests((commandContext, suggestionsBuilder) -> suggestGroups(suggestionsBuilder))
                                        .executes(Command::remove)))

                        .then(CommandManager.literal("join")
                                .then(CommandManager.argument("GroupName", StringArgumentType.string()).suggests((commandContext, suggestionsBuilder) -> suggestGroups(suggestionsBuilder))
                                        .then(CommandManager.argument("Player", EntityArgumentType.players())
                                                .executes(Command::join))))

                        .then(CommandManager.literal("leave")
                                .then(CommandManager.argument("Player", EntityArgumentType.players())
                                        .executes(Command::leave)))

                        .then(CommandManager.literal("list")
                                .executes(Command::list))

                        .then(CommandManager.literal("game")
                                .then(CommandManager.literal("set_skin_from_storage")
                                        .then(CommandManager.argument("Path", IdentifierArgumentType.identifier()).suggests((context, builder) -> CommandSource.suggestIdentifiers(context.getSource().getServer().getDataCommandStorage().getIds(), builder))
                                                .executes(context -> {
                                                    Identifier identifier = IdentifierArgumentType.getIdentifier(context, "Path");
                                                    DataCommandStorage storage = context.getSource().getServer().getDataCommandStorage();

                                                    NbtCompound nbt = storage.get(identifier);
                                                    String base64String = nbt.getString("result");

                                                    Base64.Decoder decoder = Base64.getDecoder();
                                                    byte[] bytes = decoder.decode(base64String);

                                                    NbtCompound result = new NbtCompound();
                                                    try {
                                                        JsonObject json = GSON.fromJson(new String(bytes), JsonObject.class);
                                                        String textureUrl = json.get("textures").getAsJsonObject().get("SKIN").getAsJsonObject().get("url").getAsString();

                                                        result.putString("result", textureUrl);
                                                        storage.set(identifier, result);
                                                        return 1;
                                                    } catch (Exception e) {
                                                        return 0;
                                                    }
                                                })
                                        ))

                                .then(CommandManager.literal("option") //暂时废，当时调试用
                                        .then(CommandManager.argument("Frequency", FloatArgumentType.floatArg(0.0f))
                                                .then(CommandManager.argument("Amplitude", IntegerArgumentType.integer(0))
                                                        .then(CommandManager.argument("RandomFactor", FloatArgumentType.floatArg(0.0f))
                                                                .then(CommandManager.argument("Addon", IntegerArgumentType.integer())
                                                                        .executes(Command::option))))))

                                .then(CommandManager.literal("pop")
                                        .then(CommandManager.literal("bug")
                                                .then(CommandManager.argument("Player", EntityArgumentType.player())
                                                        .then(CommandManager.argument("Time", IntegerArgumentType.integer(0))
                                                                .executes(Command::popBug))))

                                        .then(CommandManager.literal("effect_rdm_clear")
                                                .then(CommandManager.argument("Players", EntityArgumentType.players())
                                                        .executes(Command::popEffectRdmClear)))
                                )

                                .then(CommandManager.literal("channel")
                                        .then(CommandManager.argument("Players", EntityArgumentType.players())
                                                .then(CommandManager.argument("Channel", IntegerArgumentType.integer(-1, 2))
                                                        .executes(Command::channel)))))

                        .then(CommandManager.literal("player")
                                .then(CommandManager.literal("grave")
                                        .then(CommandManager.argument("Player", EntityArgumentType.player())
                                                .executes(Command::playerGrave))))
                );
    }

    private static int create(CommandContext<ServerCommandSource> context) {
        String groupName = StringArgumentType.getString(context, "GroupName");
        VoicechatServerApi api = Plugin.voicechatServerApi;

        if (Plugin.groups.containsKey(groupName)) {

            context.getSource().sendError(Text.translatable("commands.killergroup.create.repeat", groupName));
            return 0;

        } else {
            String groupType = StringArgumentType.getString(context, "GroupType");

            Group group = api.groupBuilder()
                    .setName(groupName)
                    .setId(UUID.randomUUID())
                    .setType(GroupType.valueOf(groupType.toUpperCase()).getType())
                    .setPersistent(true)
                    .setHidden(true)
                    .setPassword(groupName)
                    .build();

            Plugin.groups.put(group.getName(), group);
            context.getSource().sendFeedback(() -> Text.translatable("commands.killergroup.create.success", groupName), true);
            return 1;
        }
    }

    private static int remove(CommandContext<ServerCommandSource> context) {
        String groupName = StringArgumentType.getString(context, "GroupName");
        VoicechatServerApi api = Plugin.voicechatServerApi;

        if (Plugin.groups.containsKey(groupName)) {
            Group group = Plugin.groups.get(groupName);
            boolean bool = api.removeGroup(group.getId());

            if (bool) {
                context.getSource().sendFeedback(() -> Text.translatable("commands.killergroup.remove.success", groupName), true);
                return 1;
            } else {
                context.getSource().sendError(Text.translatable("commands.killergroup.remove.fail", groupName));
                return 0;
            }
        } else {
            context.getSource().sendError(Text.translatable("commands.killergroup.empty", groupName));
            return 0;
        }
    }

    private static int join(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
        Collection<ServerPlayerEntity> players = EntityArgumentType.getPlayers(context, "Player");
        if (!players.isEmpty()) {
            String groupName = StringArgumentType.getString(context, "GroupName");
            Group group = Plugin.groups.get(groupName);
            if (Plugin.groups.containsKey(groupName)) {
                for (ServerPlayerEntity player : players) {

                    GroupManager.update(player.getUuid(), group);
                }

                context.getSource().sendFeedback(() -> Text.translatable("commands.killergroup.join", toPlayersString(players), groupName), true);
                return 1;

            } else {
                context.getSource().sendError(Text.translatable("commands.killergroup.empty", groupName));
                return 0;
            }
        } else {
            context.getSource().sendError(Text.translatable("commands.player.invalid"));
            return 0;
        }
    }

    private static int leave(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
        Collection<ServerPlayerEntity> players = EntityArgumentType.getPlayers(context, "Player");

        if (!players.isEmpty()) {
            for (ServerPlayerEntity player : players) {
                GroupManager.update(player.getUuid(), null);
            }

            context.getSource().sendFeedback(() -> Text.translatable("commands.killergroup.leave", toPlayersString(players)), true);
            return 1;
        } else {
            context.getSource().sendError(Text.translatable("commands.player.invalid"));
            return 0;
        }
    }

    private static int list(CommandContext<ServerCommandSource> context) {
        String groups = Arrays.toString(Plugin.groups.keySet().toArray());
        context.getSource().sendFeedback(() -> Text.translatable("commands.killergroup.list", groups), true);
        return 1;
    }

    private static int option(CommandContext<ServerCommandSource> context) {
        float frequency = FloatArgumentType.getFloat(context, "Frequency");
        int amplitude = IntegerArgumentType.getInteger(context, "Amplitude");
        float randomFactor = FloatArgumentType.getFloat(context, "RandomFactor");
        int addon = IntegerArgumentType.getInteger(context, "Addon");

        Plugin.VOICE_OPTION = new Plugin.VoiceOption(frequency, amplitude, randomFactor, addon);
        return 1;
    }

    private static int channel(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
        Collection<ServerPlayerEntity> players = EntityArgumentType.getPlayers(context, "Players");

        if (players.isEmpty()) {
            context.getSource().sendError(Text.translatable("commands.player.invalid"));
            return 0;
        } else {
            int channel = IntegerArgumentType.getInteger(context, "Channel");

            for (ServerPlayerEntity player : players) {
                ServerPlayNetworking.send(player, new Packets.Channel(channel));

                if (channel == 2) {
                    GroupManager.update(player.getUuid(), GroupManager.DEATH_GROUP);
                } else if (channel == -1 || channel == 0) {
                    GroupManager.update(player.getUuid(), null);
                }
            }

            context.getSource().sendFeedback(() -> Text.translatable("commands.killergroup.game.channel.success", toPlayersString(players), channel), true);
            return 1;
        }
    }

    private static int popBug(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
        ServerPlayerEntity player = EntityArgumentType.getPlayer(context, "Player");
        if (player != null) {
            int time = IntegerArgumentType.getInteger(context, "Time");
            UUID uuid = player.getUuid();
            VoicechatServerApi api = Plugin.voicechatServerApi;
            VoicechatConnection connection = api.getConnectionOf(uuid);

            if (connection != null) {
                Bug.execute(player, time);

                context.getSource().sendFeedback(() -> Text.translatable("commands.killergroup.game.pop.bug", player.getName(), time), true);
                return 1;
            }
        }
        context.getSource().sendError(Text.translatable("commands.player.invalid"));
        return 0;
    }

    private static int popEffectRdmClear(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
        Collection<ServerPlayerEntity> players = EntityArgumentType.getPlayers(context, "Players");
        if (!players.isEmpty()) {
            for (ServerPlayerEntity player : players) {
                Map<RegistryEntry<StatusEffect>, StatusEffectInstance> effects = player.getActiveStatusEffects();
                List<RegistryEntry<StatusEffect>> toRemove = new ArrayList<>(effects.keySet());
                int target = (int) (Math.random() * effects.size());

                player.removeStatusEffect(toRemove.get(target));
            }

            context.getSource().sendFeedback(() -> Text.translatable("commands.killergroup.game.pop.effect_rdm_clear", toPlayersString(players)), true);
            return 1;
        } else {
            context.getSource().sendError(Text.translatable("commands.player.invalid"));
            return 0;
        }
    }

    private static int playerGrave(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
        ServerPlayerEntity player = EntityArgumentType.getPlayer(context, "Player");
        if (player != null) {
            ItemStack playerSkull = new ItemStack(Items.PLAYER_HEAD);
            playerSkull.set(DataComponentTypes.PROFILE, new ProfileComponent(player.getGameProfile()));
            ItemEntity playerHeadItemEntity = new ItemEntity(player.getWorld(), player.getX(), player.getEyeY(), player.getZ(), playerSkull);
            setGraveItemDrop(playerHeadItemEntity, 0.1f, 0.24f);
            player.getWorld().spawnEntity(playerHeadItemEntity);

            for (int i = 0; i < 5; i++) {
                ItemStack playerBone = new ItemStack(Items.BONE);
                ItemEntity playerBoneItemEntity = new ItemEntity(player.getWorld(), player.getX(), player.getRandomBodyY(), player.getZ(), playerBone);
                setGraveItemDrop(playerBoneItemEntity, 0.2f, 0.1f);
                player.getWorld().spawnEntity(playerBoneItemEntity);
                NbtCompound tag = new NbtCompound();
                tag.putShort("Age", (short) 5940);
                NbtCompound tag2 = NbtPredicate.entityToNbt(playerBoneItemEntity).copy().copyFrom(tag);
                playerBoneItemEntity.readNbt(tag2);
            }

            context.getSource().getWorld().spawnParticles(new BlockStateParticleEffect(ParticleTypes.BLOCK, Blocks.REDSTONE_BLOCK.getDefaultState()), player.getX(), player.getEyeY(), player.getZ(), 40, 0.1, 0.1, 0.1, 0);

            return 1;
        } else {
            context.getSource().sendError(Text.translatable("commands.player.invalid"));
            return 0;
        }
    }

    private static void setGraveItemDrop(ItemEntity itemEntity,float x, float y) {
        float f = itemEntity.getRandom().nextFloat() * x;
        float g = itemEntity.getRandom().nextFloat() * (float) (Math.PI * 2);
        itemEntity.setVelocity(-MathHelper.sin(g) * f, y, MathHelper.cos(g) * f);
        itemEntity.setPickupDelayInfinite();
        itemEntity.addCommandTag("killergroup_grave");
    }

    private static String toPlayersString(Collection<ServerPlayerEntity> players) {
        StringBuilder playerList = new StringBuilder();
        if (players.size() == 1) {
            return players.iterator().next().getNameForScoreboard();
        } else {
            for (ServerPlayerEntity player : players) {
                playerList.append(player.getNameForScoreboard()).append(", ");
            }
            return playerList.toString();
        }
    }

    public enum GroupType {

        NORMAL(Group.Type.NORMAL),
        OPEN(Group.Type.OPEN),
        ISOLATED(Group.Type.ISOLATED);

        private final Group.Type type;

        GroupType(Group.Type type) {
            this.type = type;
        }

        public Group.Type getType() {
            return type;
        }
    }

    private static CompletableFuture<Suggestions> suggestGroupType(SuggestionsBuilder builder) {
        List<String> poseOptions = Arrays.stream(GroupType.values()).map(GroupType::name).map(String::toLowerCase).toList();
        String remaining = builder.getRemaining().toLowerCase();
        poseOptions.stream()
                .filter(option -> option.startsWith(remaining))
                .forEach(builder::suggest);
        return builder.buildFuture();
    }

    private static CompletableFuture<Suggestions> suggestGroups(SuggestionsBuilder builder) {
        String[] strings = Plugin.groups.keySet().toArray(new String[0]);
        return CommandSource.suggestMatching(strings, builder);
    }
}
